using System;
using Lidgren.Library.Network;
using System.Collections.Generic;
using SharpWind.Network;
using System.Net;
using System.Text;

namespace SharpWindServer
{
	public class Server
	{
		private static readonly Server instance = new Server();

		private NetServer server = null;

		private NetLog log = null;

		private NetAppConfiguration config = null;

		private Dictionary<string, Connection> players = new Dictionary<string, Connection>();

		private int maxConnections = 2;

		private bool passwordEnabled = false;

		private string password = string.Empty;

		public event ConnectionEventHandler PlayerConnected = null;

		public event ConnectionEventHandler PlayerDisconnected = null;

		public event StatusEventHandler ServerStarted = null;

		public event StatusEventHandler ServerShutdown = null;

		public event NetworkLogEventHandler Log = null;

		private Server()
		{
			this.log = new NetLog();
			this.log.IgnoreTypes = NetLogEntryType.Verbose;
			this.log.LogEvent	+= new EventHandler<NetLogEventArgs>( OnLogEvent );

			this.config = new NetAppConfiguration( "SharpWind", 5070 );
			this.config.ServerName = "SharpWind Test Server";
		}

		public void Start()
		{
			if( !IsStarted )
			{
				this.config.MaximumConnections = this.maxConnections;

				this.server = new NetServer( this.config, this.log );
				this.server.StatusChanged		+= new EventHandler<NetStatusEventArgs>( OnStatusChanged );
				this.server.ConnectionRequest	+= new EventHandler<NetConnectRequestEventArgs>( OnConnectionRequest );

				OnServerStarted( new StatusEventArgs( Reason.None ) );
			}
		}

		public void Shutdown()
		{
			Shutdown( Reason.None );
		}

		public void Shutdown( string reason )
		{
			if( IsStarted )
			{
				this.server.Shutdown( reason );
				this.server = null;
				this.players.Clear();

				OnServerShutdown( new StatusEventArgs( reason ) ); 
			}
		}

		internal void Update()
		{
			if( IsStarted )
			{
				this.server.Heartbeat();

				NetMessage msg;

				while( ( msg = this.server.ReadMessage() ) != null )
				{
					HandleMessage( msg );
				}
			}
		}

		private void HandleMessage( NetMessage msg )
		{
			string header = msg.ReadString();
			if( header.Equals( "sw" ) )
			{
				byte packetType = msg.ReadByte();
				if( (PacketType)packetType == PacketType.Engine )
				{
					byte packetCommand = msg.ReadByte();
					switch( (PacketCommand)packetCommand )
					{
						case PacketCommand.Voice:
						{
							foreach( Connection connection in this.players.Values )
							{
								this.server.SendMessage( msg, connection, NetChannel.Sequenced1 );
							}

							break;
						}
					}
				}
				else
				{
				}
			}
		}

		private Connection FindConnectionByEndPoint( IPEndPoint endPoint )
		{
			foreach( Connection connection in this.players.Values )
			{
				if( connection.EndPoint.Equals( endPoint ) )
				{
					return connection;
				}
			}

			return null;
		}

		private void OnLogEvent( object sender, NetLogEventArgs e )
		{
			OnLog( new NetworkLogEventArgs( e.Entry ) );
		}

		private void OnConnectionRequest( object sender, NetConnectRequestEventArgs e )
		{
			int index = 0;
			bool paswordIncluded = BitConverter.ToBoolean( e.CustomData, index );

			string password = string.Empty;
			string playerName = string.Empty;

			index += sizeof( bool );
			if( paswordIncluded )
			{
				int passwordLength = BitConverter.ToInt32( e.CustomData, index );
				index += sizeof( int );

				password = Encoding.Unicode.GetString( e.CustomData, index, passwordLength );
				index += passwordLength;

				int playerNameLength = BitConverter.ToInt32( e.CustomData, index );
				index += sizeof( int );

				playerName = Encoding.Unicode.GetString( e.CustomData, index, playerNameLength );
			}
			else
			{
				int playerNameLength = BitConverter.ToInt32( e.CustomData, index );
				index += sizeof( int );

				playerName = Encoding.Unicode.GetString( e.CustomData, index, playerNameLength );
			}

			if( this.passwordEnabled )
			{
				if( paswordIncluded )
				{
					if( !this.password.Equals( password ) )
					{
						e.MayConnect	= false;
						e.DenialReason	= Reason.WrongPassword;
						return;
					}
				}
				else
				{
					e.MayConnect	= false;
					e.DenialReason	= Reason.WrongPassword;
					return;
				}
			}

			if( this.players.ContainsKey( playerName ) )
			{
				e.DenialReason	= Reason.PlayerConnected;
				e.MayConnect	= false;
			}
			else
			{
				this.players.Add( playerName, new Connection( playerName, e.EndPoint ) );
				e.MayConnect = true;
			}
		}

		private void OnStatusChanged( object sender, NetStatusEventArgs e )
		{
			switch( e.PreviousStatus )
			{
				case NetConnectionStatus.Connecting:
				{
					if( e.Connection.Status == NetConnectionStatus.Connected )
					{
						// Player connected
						Connection connection = FindConnectionByEndPoint( e.Connection.RemoteEndpoint );
						if( connection != null )
						{
							connection.SetConnection( e.Connection );
							OnPlayerConnected( new ConnectionEventArgs( e.Reason, connection ) );
						}
					}

					break;
				}

				case NetConnectionStatus.Connected:
				{
					// Player disconnected
					if( e.Connection.Status == NetConnectionStatus.Disconnected )
					{
						Connection connection = FindConnectionByEndPoint( e.Connection.RemoteEndpoint );
						if( connection != null )
						{
							this.players.Remove( connection.PlayerName );
							OnPlayerDisconnected( new ConnectionEventArgs( e.Reason, connection ) );
						}
					}

					break;
				}
			}
		}

		private void OnPlayerConnected( ConnectionEventArgs e )
		{
			if( PlayerConnected != null )
			{
				PlayerConnected( instance, e );
			}
		}

		private void OnPlayerDisconnected( ConnectionEventArgs e )
		{
			if( PlayerDisconnected != null )
			{
				PlayerDisconnected( instance, e );
			}
		}

		private void OnServerStarted( StatusEventArgs e )
		{
			if( ServerStarted != null )
			{
				ServerStarted( instance, e );
			}
		}

		private void OnServerShutdown( StatusEventArgs e )
		{
			if( ServerShutdown != null )
			{
				ServerShutdown( instance, e );
			}
		}

		private void OnLog( NetworkLogEventArgs e )
		{
			if( Log != null )
			{
				Log( instance, e );
			}
		}

		public static Server Instance
		{
			get
			{
				return instance;
			}
		}

		public bool IsStarted
		{
			get
			{
				return this.server != null;
			}
		}

		public Connection this[string name]
		{
			get
			{
				return this.players[name];
			}
		}

		public int MaxConnections
		{
			get
			{
				return this.maxConnections;
			}
			set
			{
				this.maxConnections = value;
			}
		}

		public bool PasswordEnabled
		{
			get
			{
				return this.passwordEnabled;
			}
			set
			{
				this.passwordEnabled = value;
			}
		}

		public string Password
		{
			get
			{
				return this.password;
			}
			set
			{
				this.password = value;
			}
		}
	}
}
